/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * Description: Hmfs-tools compare data
 */

#include "whole_compare.h"

#include <cinttypes>
#include <cmath>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <sys/types.h>

#include "hmfs_log.h"
#include "hmfs_utils.h"
#include "io.h"
#include "securec.h"

namespace OHOS {
namespace Hmfs {
std::map<uint64_t, std::vector<SkipPara>> g_skipList;

int32_t FsBinaryCompare(const std::string &src, const std::string &dst, bool isResizeFinished)
{
#ifndef HMFS_TOOLS_ARM64
    OHOS::Hmfs::CompareCmdParser tmp;
    FsData srcInfo(src);
    FsData dstInfo(dst);

    HMFS_INFO("Compare %s to %s.", src.c_str(), dst.c_str());

    if (GetSuperBlockInfo(srcInfo)) {
        return -1;
    }

    if (GetSuperBlockInfo(dstInfo)) {
        return -1;
    }

    AddSuperBlockSkipPara(srcInfo);
#ifdef HMFS_UNIT_TEST_RESIZE
    AddCheckPointSkipParaForResize(srcInfo, isResizeFinished);
#else
    AddCheckPointSkipParaForMkfs(srcInfo);
#endif

    AddMainAreaSkipPara(srcInfo);

    if (BinaryCompareFs(srcInfo, dstInfo)) {
        g_skipList.clear();
        return -1;
    }
    g_skipList.clear();

    HMFS_INFO("Compare OK.");
#endif
    return 0;
}

int32_t GetSuperBlockInfo(FsData &fsInfo)
{
    std::ifstream file(fsInfo.path.c_str(), std::ios::in);
    if (!file) {
        HMFS_ERROR("Failed to open file, errno %d", errno);
        return -1;
    }

    fsInfo.superBlockBuf = std::make_unique<char[]>(HMFS_BLOCK_SIZE);
    file.read(fsInfo.superBlockBuf.get(), HMFS_BLOCK_SIZE);
    if (!file) {
        HMFS_ERROR("Failed to read super block.");
        return -1;
    }

    fsInfo.superBlock = reinterpret_cast<SuperBlockData*>(fsInfo.superBlockBuf.get() + HMFS_SUPER_BLOCK_OFFSET);

    for (int32_t qtype = 0; qtype < MAX_QUOTAS; qtype++) {
        if (fsInfo.superBlock->quotaInodeId[qtype] != 0) {
            fsInfo.quotaCount++;
            fsInfo.quotaBits |= 1 << qtype;
        }
    }

    fsInfo.features = GetLeValue(fsInfo.superBlock->features);
    for (uint32_t i = 0; i < MAX_DEVICE_COUNT; i++) {
        if (fsInfo.superBlock->devices[i].path[0] == 0) {
            break;
        }

        DevInfo deviceInfo;
        deviceInfo.path = fsInfo.superBlock->devices[i].path;
        deviceInfo.segmentCount = GetLeValue(fsInfo.superBlock->devices[i].segmentCount);
        if (i == 0) {
            deviceInfo.startBlkId = 0;
            deviceInfo.endBlkId = deviceInfo.segmentCount * BLOCKS_PER_SEGMENT - 1 +
                GetLeValue(fsInfo.superBlock->segment0BlockId);
        } else {
            DevInfo& prevDevice = fsInfo.deviceList[i - 1];
            deviceInfo.startBlkId = prevDevice.endBlkId + 1;
            deviceInfo.endBlkId = deviceInfo.startBlkId + deviceInfo.segmentCount * BLOCKS_PER_SEGMENT - 1;
        }
        fsInfo.deviceList.emplace_back(deviceInfo);

        HMFS_DEBUG("dev[%u]: segmentCount = %u, startBlkId = 0x%" PRIx64 ", endBlkId = 0x%" PRIx64 ".",
            i, deviceInfo.segmentCount, deviceInfo.startBlkId, deviceInfo.endBlkId);
    }

    return 0;
}

void AddSuperBlockSkipPara(FsData &srcInfo)
{
    uint32_t offset = 1024 + offsetof(SuperBlockData, uuid);
    AddSkipPara(0, offset, UUID_SIZE); // uuid in SuperBlockData
    AddSkipPara(1, offset, UUID_SIZE);

    if (srcInfo.superBlock->devices[0].path[0] != 0) {
        for (uint32_t i = 0; i < MAX_DEVICE_COUNT; i++) {
            offset = 1024 + offsetof(SuperBlockData, devices) + sizeof(HmfsDevice) * i;
            AddSkipPara(0, offset, DEVICE_PATH_MAX_LEN); // devices[i].path in SuperBlockData
            AddSkipPara(1, offset, DEVICE_PATH_MAX_LEN);
        }
    }

    if (srcInfo.superBlock->checksum != 0) {
        offset = 1024 + offsetof(SuperBlockData, checksum);
        AddSkipPara(0, offset, sizeof(uint32_t)); // checksum in SuperBlockData
        AddSkipPara(1, offset, sizeof(uint32_t));
    }
}

void AddCheckPointSkipParaForMkfs(FsData &srcInfo)
{
    uint32_t startBlockid = GetLeValue(srcInfo.superBlock->checkPointBlockId);
    uint32_t checkPointPayload = GetLeValue(srcInfo.superBlock->checkPointPayload);
    uint32_t secondCpBlockId = startBlockid + checkPointPayload + 5;

    uint32_t offset = offsetof(CheckPointData, checkPointVersion);
    AddSkipPara(startBlockid, offset, sizeof(uint64_t)); // checkPointVersion in CheckPointData
    AddSkipPara(secondCpBlockId, offset, sizeof(uint64_t));

    auto buf = std::make_unique<char[]>(HMFS_BLOCK_SIZE);
    if (DevRead(srcInfo, buf.get(), startBlockid * HMFS_BLOCK_SIZE, HMFS_BLOCK_SIZE)) {
        HMFS_ERROR("Failed to read src check point at blockId 0x%x.", startBlockid);
        return;
    }
    auto cpData = reinterpret_cast<CheckPointData *>(buf.get());
    uint32_t checksumOffset = GetLeValue(cpData->checksumOffset);
    AddSkipPara(startBlockid, checksumOffset, sizeof(uint32_t)); // checksum in CheckPointData
    AddSkipPara(secondCpBlockId, checksumOffset, sizeof(uint32_t));

    uint32_t natBitmapSize = GetLeValue(srcInfo.superBlock->segmentCountInNAT) / 2 * 512 / 8;
    uint32_t natBitsAreablocks = AlignUpCount(sizeof(uint64_t) + natBitmapSize + natBitmapSize, HMFS_BLOCK_SIZE);
    uint32_t natBitsStartblockId = BLOCKS_PER_SEGMENT - natBitsAreablocks + startBlockid;

    AddSkipPara(natBitsStartblockId, 0, sizeof(uint64_t)); // crc in natBitmap

    srcInfo.curSeg[CURSEG_NODE_HOT] = GetLeValue(cpData->curNodeSegNo[0]);
    srcInfo.curSeg[CURSEG_NODE_WARM] = GetLeValue(cpData->curNodeSegNo[1]);
    srcInfo.curSeg[CURSEG_NODE_COLD] = GetLeValue(cpData->curNodeSegNo[2]);
    srcInfo.curSeg[CURSEG_DATA_HOT] = GetLeValue(cpData->curDataSegNo[0]);
    srcInfo.curSeg[CURSEG_DATA_WARM] = GetLeValue(cpData->curDataSegNo[1]);
    srcInfo.curSeg[CURSEG_DATA_COLD] = GetLeValue(cpData->curDataSegNo[2]);
}

void AddCheckPointSkipParaForResize(FsData &srcInfo, bool isResizeFinished)
{
    uint32_t startBlockid = GetLeValue(srcInfo.superBlock->checkPointBlockId);
    uint32_t startBlockidSeg2 = startBlockid + (1 << GetLeValue((srcInfo.superBlock->logBlksPerSeg)));
    uint32_t checkPointPayload = GetLeValue(srcInfo.superBlock->checkPointPayload);

    auto buf = std::make_unique<char[]>(HMFS_BLOCK_SIZE);   
    if (DevRead(srcInfo, buf.get(), startBlockid * HMFS_BLOCK_SIZE, HMFS_BLOCK_SIZE)) {
        HMFS_ERROR("Failed to read src check point at blockId 0x%x.", startBlockid);
        return;
    }
    auto buf2 = std::make_unique<char[]>(HMFS_BLOCK_SIZE);
    if (DevRead(srcInfo, buf2.get(), startBlockidSeg2 * HMFS_BLOCK_SIZE, HMFS_BLOCK_SIZE)) {
        HMFS_ERROR("Failed to read src check point2 at blockId 0x%x.", startBlockid);
        return;
    }

    auto cpData = reinterpret_cast<CheckPointData *>(buf.get());
    auto cpData2 = reinterpret_cast<CheckPointData *>(buf2.get());
    uint32_t flags = 0;
    uint32_t flags2 = 0;
    flags = GetLeValue(cpData->checkPointFlags);
    std::cout << "cpData flags = " << std::hex << flags << std::endl;
    flags2 = GetLeValue(cpData2->checkPointFlags);
    std::cout << "cpData2 flags2 = " << std::hex << flags2 << std::endl;
    uint32_t blockOffset = 7; // resize成功，会增加了两个 blockOffset
    if (!isResizeFinished) {
        blockOffset = 5; // 没有resize成功，blockOffset默认为5个
    }
    uint32_t secondCpBlockId = startBlockid + checkPointPayload + blockOffset;
    uint32_t secondCpBlockIdSeg2 = startBlockidSeg2 + checkPointPayload + blockOffset;

    uint32_t offset = offsetof(CheckPointData, checkPointVersion);
    AddSkipPara(startBlockid, offset, sizeof(uint64_t)); // checkPointVersion in CheckPointData
    AddSkipPara(secondCpBlockId, offset, sizeof(uint64_t));
    AddSkipPara(startBlockidSeg2, offset, sizeof(uint64_t)); // checkPointVersion in CheckPointData in segment2
    AddSkipPara(secondCpBlockIdSeg2, offset, sizeof(uint64_t));

    // CP_FLAG_LARGE_NAT_BITMAP参数不同，checksum位置不同
    AddSkipPara(startBlockid, CP_MIN_CHECKSUM_OFFSET, sizeof(uint32_t)); // checksum in CheckPointData
    AddSkipPara(startBlockid, CP_CHECKSUM_OFFSET, sizeof(uint32_t));
    AddSkipPara(secondCpBlockId, CP_MIN_CHECKSUM_OFFSET, sizeof(uint32_t));
    AddSkipPara(secondCpBlockId, CP_CHECKSUM_OFFSET, sizeof(uint32_t));
    AddSkipPara(startBlockidSeg2, CP_MIN_CHECKSUM_OFFSET, sizeof(uint32_t)); // checksum in CheckPointData in segment2
    AddSkipPara(startBlockidSeg2, CP_CHECKSUM_OFFSET, sizeof(uint32_t));
    AddSkipPara(secondCpBlockIdSeg2, CP_MIN_CHECKSUM_OFFSET, sizeof(uint32_t));
    AddSkipPara(secondCpBlockIdSeg2, CP_CHECKSUM_OFFSET, sizeof(uint32_t));

    uint32_t natBitmapSize = GetLeValue(srcInfo.superBlock->segmentCountInNAT) / 2 * 512 / 8;
    uint32_t natBitsAreablocks = AlignUpCount(sizeof(uint64_t) + natBitmapSize + natBitmapSize, HMFS_BLOCK_SIZE);
    uint32_t natBitsStartblockId = BLOCKS_PER_SEGMENT - natBitsAreablocks + startBlockid;
    uint32_t natBitsStartblockIdSeg2 = BLOCKS_PER_SEGMENT - natBitsAreablocks + startBlockidSeg2;
    AddSkipPara(natBitsStartblockId, 0, sizeof(uint64_t));  // crc in natBitmap
    AddSkipPara(natBitsStartblockIdSeg2, 0, sizeof(uint64_t));  // crc in natBitmap in segment2

    srcInfo.curSeg[CURSEG_NODE_HOT] = GetLeValue(cpData->curNodeSegNo[0]);
    srcInfo.curSeg[CURSEG_NODE_WARM] = GetLeValue(cpData->curNodeSegNo[1]);
    srcInfo.curSeg[CURSEG_NODE_COLD] = GetLeValue(cpData->curNodeSegNo[2]);
    srcInfo.curSeg[CURSEG_DATA_HOT] = GetLeValue(cpData->curDataSegNo[0]);
    srcInfo.curSeg[CURSEG_DATA_WARM] = GetLeValue(cpData->curDataSegNo[1]);
    srcInfo.curSeg[CURSEG_DATA_COLD] = GetLeValue(cpData->curDataSegNo[2]);
}

void AddMainAreaSkipPara(FsData &srcInfo)
{
    uint64_t inodeStartBlockId = GetLeValue(srcInfo.superBlock->mainBlockId) +
        srcInfo.curSeg[CURSEG_NODE_HOT] * BLOCKS_PER_SEGMENT;
    uint32_t inodeBlockCount = 1 + srcInfo.quotaCount + ((GetLeValue(srcInfo.features) &
        HMFS_FEATURE_LOST_FOUND) ? 1 : 0);

    for (uint64_t blockId = inodeStartBlockId; blockId < inodeStartBlockId + inodeBlockCount; blockId++) {
        AddSkipPara(blockId, offsetof(HmfsInode, accessTime), sizeof(uint64_t)); // accessTime in HmfsInode
        AddSkipPara(blockId, offsetof(HmfsInode, changeTime), sizeof(uint64_t));  // changeTime in HmfsInode
        AddSkipPara(blockId, offsetof(HmfsInode, modificationTime), sizeof(uint64_t)); // modificationTime in HmfsInode

        if (GetLeValue(srcInfo.features) & HMFS_FEATURE_INODE_CHKSUM) {
            AddSkipPara(blockId, offsetof(HmfsInode, inodeChecksum), sizeof(uint32_t)); // inodeChecksum in HmfsInode
        }

        if (GetLeValue(srcInfo.features) & HMFS_FEATURE_INODE_CRTIME) {
            AddSkipPara(blockId, offsetof(HmfsInode, creationTime), sizeof(uint64_t)); // creationTime in HmfsInode
        }
    }
}

void AddSkipPara(uint64_t blockId, uint32_t offset, uint32_t len)
{
    if (offset > HMFS_BLOCK_SIZE) {
        HMFS_ERROR("Invalid offset %u.", offset);
        return;
    }
    g_skipList[blockId].push_back({offset, len});
}

void ShowSkipList()
{
    printf("\nSkip list : \n");
    for (const auto& skipBlock : g_skipList) {
        printf("BlockId : 0x%" PRIx64 "\n", skipBlock.first);
        for (const auto& skipList : skipBlock.second) {
            printf("    offset = %u, len = %u\n", skipList.offset, skipList.len);
        }
    }
}

int32_t BinaryCompareFs(FsData &srcInfo, FsData &dstInfo)
{
    ShowSkipList();

    srcInfo.blockBuf = std::make_unique<char[]>(HMFS_BLOCK_SIZE);
    dstInfo.blockBuf = std::make_unique<char[]>(HMFS_BLOCK_SIZE);
    uint64_t blockCount = GetLeValue(srcInfo.superBlock->segmentCount) * BLOCKS_PER_SEGMENT +
        GetLeValue(srcInfo.superBlock->segment0BlockId);
    HMFS_INFO("total block count 0x%" PRIx64 ".", blockCount);
    for (uint64_t blockId = 0; blockId < blockCount; blockId++) {
        if (DevRead(srcInfo, srcInfo.blockBuf.get(), blockId * HMFS_BLOCK_SIZE, HMFS_BLOCK_SIZE)) {
            HMFS_ERROR("Failed to read src at blockId 0x%" PRIx64 ".", blockId);
            return -1;
        }

        if (DevRead(dstInfo, dstInfo.blockBuf.get(), blockId * HMFS_BLOCK_SIZE, HMFS_BLOCK_SIZE)) {
            HMFS_ERROR("Failed to read dst at blockId 0x%" PRIx64 ".", blockId);
            return -1;
        }

        if (BinaryCompareOneBlock(srcInfo.blockBuf.get(), dstInfo.blockBuf.get(), blockId)) {
            HMFS_ERROR("blockId 0x%" PRIx64 " not equal", blockId);
            return -1;
        }
    }

    return 0;
}

int32_t BinaryCompareOneBlock(char *srcBuf, char *dstBuf, uint64_t blockId)
{
    auto it = g_skipList.find(blockId);
    if (it == g_skipList.end()) {
        if (memcmp(srcBuf, dstBuf, HMFS_BLOCK_SIZE)) {
            return -1;
        }
        return 0;
    }

    size_t pos = 0;
    for (const auto& skip : it->second) {
        if (memcmp(srcBuf + pos, dstBuf + pos, skip.offset - pos) != 0) {
            return -1;
        }
        pos = skip.offset + skip.len;
    }

    if (pos >= HMFS_BLOCK_SIZE) {
        return 0;
    }
    return memcmp(srcBuf + pos, dstBuf + pos, HMFS_BLOCK_SIZE - pos);
}

} // namespace Hmfs
} // namespace OHOS
